package cn.wizzer.app.user.modules.service.impl;

import cn.wizzer.app.crown.modules.models.enums.SettleStatusEnum;
import cn.wizzer.app.crown.modules.models.vo.CrownUserOrderVo;
import cn.wizzer.app.sys.modules.services.SysParamService;
import cn.wizzer.app.user.modules.models.User;
import cn.wizzer.app.user.modules.models.UserAccount;
import cn.wizzer.app.user.modules.models.UserAccountRecord;
import cn.wizzer.app.user.modules.models.enums.BizTypeEnum;
import cn.wizzer.app.user.modules.models.vo.AccountRecordVo;
import cn.wizzer.app.user.modules.services.UserRestrictionService;
import cn.wizzer.app.user.modules.services.UserService;
import cn.wizzer.app.user.modules.services.account.UserAccountRecordService;
import cn.wizzer.app.user.modules.services.account.UserAccountService;
import cn.wizzer.framework.base.Result;
import cn.wizzer.framework.base.exceptions.BizException;
import cn.wizzer.framework.base.service.BaseServiceImpl;
import cn.wizzer.framework.base.service.CommonDBService;
import cn.wizzer.framework.page.Pagination;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.annotation.Service;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.Times;
import org.nutz.log.Log;
import org.nutz.log.Logs;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@IocBean(args = {"refer:dao"})
@Service(interfaceClass = UserAccountRecordService.class)
public class UserAccountRecordServiceImpl extends BaseServiceImpl<UserAccountRecord> implements UserAccountRecordService {

    private final static Log log = Logs.get();

    public UserAccountRecordServiceImpl(Dao dao) {
        super(dao);
    }

    @Inject
    private CommonDBService dbService;

    @Inject
    private UserRestrictionService restrictionService;

    @Inject
    private UserAccountService accountService;

    @Inject
    @Reference
    private SysParamService sysParamService;

    @Inject
    private UserService userService;

    @Aop(TransAop.REPEATABLE_READ)
    @Override
    public void add(String userId, BigDecimal amount, BizTypeEnum bizType, String bizId) {
        try {
            log.info("start ===>user:" + userId + " bizId :" + bizId);
            if (BigDecimal.ZERO.compareTo(amount) >= 0) {
                Result.breakWith("请确保您的行为安全");
            }
            //向下取保留六位小数
            amount = amount.setScale(2, RoundingMode.DOWN);
            UserAccount account = accountService.selectByUserId(userId);

            //支出项
            if (bizType.VALUE < 0) {
                //取反
                amount = amount.negate();
            }
            //当前余额
            BigDecimal currentRemain = amount.add(account.getBalance());

            // 扣除结算金额、重新结算不用校验账户余额不足。
            boolean cnd = !(bizType.equals(BizTypeEnum.BALL_DEDUCT) || bizType.equals(BizTypeEnum.BALL_RESETTLE));
            if (cnd && BigDecimal.ZERO.compareTo(currentRemain) > 0) {
                Result.breakWith("账户余额不足");
            }

            log.info("user:" + userId + " account remain change :" + amount);
            UserAccountRecord userAccountRecord = new UserAccountRecord();
            userAccountRecord.setCtAt(System.currentTimeMillis());
            userAccountRecord.setUserId(userId);
            userAccountRecord.setBizId(bizId);
            userAccountRecord.setBizType(bizType);
            userAccountRecord.setOriginRemain(account.getBalance());
            userAccountRecord.setOperateAmount(amount);
            userAccountRecord.setCurrentRemain(currentRemain);
            this.insert(userAccountRecord);
            //修改余额
            account.setBalance(currentRemain);
            account.setTotal(account.getTotal().add(amount));
            accountService.update(account);
            log.info("end ===>user:" + userId + " bizId :" + bizId);
        } catch (BizException be) {
            throw be;
        } catch (Exception e) {
            log.error(e.getMessage());
            Result.breakWith("出现异常");
        }
    }

    @Override
    public Pagination listPage(Integer pageNumber, int pageSize, String userId, BizTypeEnum bizType, String[] searchDate) {
        Sql sql = Sqls.create("SELECT id, user_id, operate_amount, origin_remain, current_remain, " +
                "biz_type, biz_id, ctAt, counted, opBy, opAt, delFlag " +
                "FROM user_account_record " +
                "$condition ");
        Sql countSql = Sqls.create("SELECT COUNT(1) FROM user_account_record " +
                "$condition ");
        Cnd cnd = Cnd.NEW();
        if (Strings.isNotBlank(userId)) {
            cnd.and("user_id", "=", userId);
        }
        if (bizType != null) {
            cnd.and("biz_type", "=", bizType);
        }
        if (searchDate != null && searchDate.length > 0) {
            cnd.and("opAt", ">=", Times.d2TS(Times.D(searchDate[0])));
            cnd.and("opAt", "<=", Times.d2TS(Times.D(searchDate[1])));
        }
        sql.setCondition(cnd);
        sql.appendSourceSql("order by opAt desc");
        countSql.setCondition(cnd);

        Pagination pagination = dbService.listPage(AccountRecordVo.class, pageNumber, pageSize, sql.toString(), countSql.toString());
        List<AccountRecordVo> accountRecordVoList = pagination.getList(AccountRecordVo.class);
        if (accountRecordVoList.size() > 0) {
            //循环赋予用户信息
            List<String> userIds = accountRecordVoList.stream().map(AccountRecordVo::getUserId).distinct().collect(Collectors.toList());
            Map<String, User> userMap = userService.findByIds(userIds);
            for (AccountRecordVo accountRecordVo : accountRecordVoList) {
                User user = userMap.get(accountRecordVo.getUserId());
                accountRecordVo.setPhone(user.getPhone());
                accountRecordVo.setNickName(user.getNickName());
            }
        }
        return pagination;
    }

    @Override
    public Map count(int pageNumber, int pageSize, String searchKeyword, long startTime, long endTime, String no, SettleStatusEnum status, String appid) {
        Sql sql = Sqls.create("SELECT o.no, o.status, o.play_way, u.nickName, u.userName, u.phone, u.id as uId, u.headPicture, o.settleAt, o.opAt, ABS(o.amount) as operateAmount, v.odds " +
                "FROM crown_order o " +
                "LEFT JOIN (SELECT cod.order_id, CASE co.play_way WHEN 'P3' THEN '0' ELSE cod.odds END as odds " +
                "   FROM crown_order_detail cod " +
                "   LEFT JOIN crown_order co ON cod.order_id = co.id " +
                "   GROUP BY cod.order_id) v ON v.order_id = o.id " +
                "LEFT JOIN USER u ON o.user_id = u.id " +
                "$condition ");
        Sql countSql = Sqls.create("SELECT COUNT(1) FROM crown_order o " +
                "LEFT JOIN USER u ON o.user_id = u.id " +
                "$condition ");
        Cnd cnd = Cnd.where("u.inviter", "=", appid);
        if (Strings.isNotBlank(searchKeyword)) {
            searchKeyword = "%" + searchKeyword + "%";
            cnd.and(Cnd.exps("u.nickName", "LIKE", searchKeyword)
                    .or("u.phone", "LIKE", searchKeyword)
                    .or("u.userName", "LIKE", searchKeyword));
        }
        if (0 != startTime) {
            cnd.and("o.settleAt", ">=", startTime);
        }
        if (0 != endTime) {
            cnd.and("o.settleAt", "<=", endTime);
        }
        if (Strings.isNotBlank(no)) {
            no = "%" + no + "%";
            cnd.and("o.no", "LIKE", no);
        }
        if (status != null) {
            cnd.and("o.status", "=", status);
        }
        sql.setCondition(cnd);
        sql.appendSourceSql("ORDER BY o.ctAt DESC");
        countSql.setCondition(cnd);
        //有效赔率
        BigDecimal validOdds = sysParamService.fetch().getValidOdds();
        //总金额
        BigDecimal countAmount = this.calculationInviterIncomeStatistics(searchKeyword, startTime, endTime, no, status, appid, BigDecimal.ZERO);
        //总有效金额
        BigDecimal validCountAmount = this.calculationInviterIncomeStatistics(searchKeyword, startTime, endTime, no, status, appid, validOdds);
        return Result.createMap("countAmount", countAmount,
                "validCountAmount", validCountAmount,
                "pagination", dbService.listPage(CrownUserOrderVo.class, pageNumber, pageSize, sql.toString(), countSql.toString()),
                "validOdds", validOdds);
    }

    @Override
    public <T> List<T> query(Class clazz, String sql) {
        return dbService.list(clazz, sql);
    }

    /**
     * 计算代理注单统计
     *
     * @param searchKeyword
     * @param startTime
     * @param endTime
     * @param appid
     * @param validOdds     有效赔率
     * @return
     */
    public BigDecimal calculationInviterIncomeStatistics(String searchKeyword, long startTime, long endTime, String no, SettleStatusEnum status, String appid, BigDecimal validOdds) {
        Sql sql = Sqls.create("SELECT IFNULL(SUM(o.amount), 0) ")
                .appendSourceSql("FROM crown_order o ")
                .appendSourceSql("LEFT JOIN (SELECT cod.order_id, CASE co.play_way WHEN 'P3' THEN '0' ELSE cod.odds END as odds " +
                        "FROM crown_order_detail cod " +
                        "LEFT JOIN crown_order co ON cod.order_id = co.id " +
                        "GROUP BY cod.order_id) v ON v.order_id = o.id ")
                .appendSourceSql("LEFT JOIN USER u ON o.user_id = u.id ")
                .appendSourceSql("WHERE u.inviter = @appid");
        sql.setParam("appid", appid);
        if (Strings.isNotBlank(searchKeyword)) {
            sql.appendSourceSql("AND (u.userName LIKE CONCAT('%',@searchKeyword,'%') " +
                    "OR u.nickName LIKE CONCAT('%',@searchKeyword,'%') " +
                    "OR u.phone LIKE CONCAT('%',@searchKeyword,'%') )");
            sql.setParam("searchKeyword", searchKeyword);
        }
        if (0 != startTime) {
            sql.appendSourceSql("AND o.settleAt >= @startTime ");
            sql.setParam("startTime", startTime);
        }
        if (0 != endTime) {
            sql.appendSourceSql("AND o.settleAt <= @endTime ");
            sql.setParam("endTime", endTime);
        }
        if (Strings.isNotBlank(no)) {
            sql.appendSourceSql("AND o.no LIKE CONCAT('%',@no,'%') ");
            sql.setParam("no", no);
        }
        if (status != null) {
            sql.appendSourceSql("AND o.status = @status ");
            sql.setParam("status", status);
        }
        if (validOdds.compareTo(BigDecimal.ZERO) > 0) {
            sql.appendSourceSql("AND (v.odds > @validOdds " +
                    "OR (v.odds = 0 AND o.play_way = 'P3'))");
            sql.setParam("validOdds", validOdds);
        }
        sql.setCallback(Sqls.callback.doubleValue());
        this.execute(sql);
        return BigDecimal.valueOf(sql.getDouble()).setScale(2, RoundingMode.DOWN);
    }
}
